using System;
using System.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
using Tycho.Grammar;
using Tycho.Parser.Peg;

namespace Tycho.UnitTests.Grammar {
    [TestFixture]
    public class SequenceRuleCompilerSpecification {
        [Test]
        public void ShouldCompileEachRuleInCompositeTerm () {
            var ruleCompiler = new Mock<IRuleCompiler> ();
            ICaptureCounter captureCounter = new Mock<ICaptureCounter> ().Object;
            var context = new ProductionCompilerContext (null, null, null, null);

            var sequence = new CompositeTerm ("seq", null);

            var rule1 = AddRule (sequence, ruleCompiler, context, captureCounter);
            var rule2 = AddRule (sequence, ruleCompiler, context, captureCounter);
            var rule3 = AddRule (sequence, ruleCompiler, context, captureCounter);
            var expectedRules = new[] {rule1, rule2, rule3};

            var infixInformationBuilder = new InfixBuilder (new Mock<IInfixInformation> ().Object);
            var compiler = new SequenceRuleCompiler (ruleCompiler.Object, () => infixInformationBuilder);

            var rule = compiler.Compile (sequence, context, captureCounter) as SequenceRule;

            Assert.That (rule, Is.Not.Null);
            Assert.That (rule.Rules, Is.EqualTo (expectedRules));
            Assert.That (rule.InfixInformation, Is.SameAs (infixInformationBuilder.InfixInformation));
            Assert.That (infixInformationBuilder.Rules, Is.EqualTo (expectedRules));
        }

        /*
        [Test]
        public void ShouldPlaceRecoveryProductionBeforeKeywordRule () {
            var ruleCompiler = new RuleCompiler (new GrammarCompiler ());
            ICaptureCounter captureCounter = new CaptureCounter ();
            var lookup = new ProductionLookup();
            lookup.Add ("id1", new IdentifierProduction ());
            lookup.Add ("id2", new IdentifierProduction ());
            var context = new ProductionCompilerContext (new IdentifierProductionCompilerEvaluator (new IdentifierProduction ()), lookup, new LazyDynamicGrammar (), new DeferredCompilations ());

            var sequenceCompiler = new SequenceRuleCompiler (ruleCompiler);

            var sequence = new CompositeTerm ("seq", null);
            sequence.Add ("rules", CreateCapture ("x", "id1"), true);
            sequence.Add ("rules", CreateCapture ("y", "id2"), true);
            sequence.Add ("rules", CreateKeyword ("keyword"), true);

            var rule = sequenceCompiler.Compile (sequence, context, captureCounter) as SequenceRule;

            Assert.That (rule, Is.Not.Null);
            Assert.That (rule.Rules.Length, Is.EqualTo (3));
            Assert.That (rule.Rules[0], Is.InstanceOf (typeof (NamedRule)));

            var yRule = rule.Rules[1] as NamedRule;
            Assert.That (yRule, Is.Not.Null);
            PunctuationRule production = yRule.Production;
            Assert.That (production, Is.Not.Null);

            Assert.That (rule.Rules[2], Is.InstanceOf (typeof (KeywordRule)));
        }*/

        private static ITerm CreateKeyword (string keyword) {
            var term = new CompositeTerm ("keyword", null);
            term.Add ("name", new StringTerm (keyword, null), false);
            return term;
        }

        private static CompositeTerm CreateCapture (string name, string productionName) {
            var id = new CompositeTerm ("capture", null);
            id.Add ("name", new IdentifierTerm (name, null), false);
            id.Add ("production", new IdentifierTerm (productionName, null), false);
            return id;
        }

/*        [Test, Explicit]
        public void ShouldPlaceRecoveryProductionBeforeKeywordRuleOld () {
            var ruleCompiler = new Mock<IRuleCompiler> ();
            ICaptureCounter captureCounter = new Mock<ICaptureCounter> ().Object;
            var context = new ProductionCompilerContext (null, null, null, null);

            var sequence = new CompositeTerm ("seq", null);

            var rule1 = AddRule (sequence, ruleCompiler, context, captureCounter);

            var recoverableRule = new Mock<IRule> ();
            IProduction recoverableProduction = new Mock<IProduction> ().Object;
            recoverableRule.SetupGet (r => r.RecoverableProduction).Returns (recoverableProduction);
            var rule2 = AddSpecificRule (sequence, recoverableRule.Object, ruleCompiler, context, captureCounter);

            var keywordRule = new Mock<IRule> ();
            IProduction recoveryProduction = new Mock<IProduction> ().Object;
            keywordRule.SetupGet (k => k.RecoveryProduction).Returns (recoveryProduction);
            var rule3 = AddSpecificRule (sequence, keywordRule.Object, ruleCompiler, context, captureCounter);

            var compiler = new SequenceRuleCompiler (ruleCompiler.Object);

            var rule = compiler.Compile (sequence, context, captureCounter) as SequenceRule;

            Assert.That (rule, Is.Not.Null);
            Assert.That (rule.Rules.Length, Is.EqualTo (3));
            Assert.That (rule.Rules [0], Is.EqualTo (rule1));
            IRule actualRecoveryRule = rule.Rules [1];
            Assert.That (actualRecoveryRule, Is.EqualTo (rule1));
            Assert.That (rule.Rules, Is.EqualTo (new[] {rule1, rule2, rule3}));
        } */

        private static IRule AddRule (CompositeTerm sequence, Mock<IRuleCompiler> ruleCompiler, ProductionCompilerContext context, ICaptureCounter captureCounter) {
            IRule rule = new Mock<IRule> ().Object;
            return AddSpecificRule (sequence, rule, ruleCompiler, context, captureCounter);
        }

        private static IRule AddSpecificRule (CompositeTerm sequence, IRule rule, Mock<IRuleCompiler> ruleCompiler, ProductionCompilerContext context, ICaptureCounter captureCounter) {
            ITerm term = new Mock<ITerm> ().Object;
            sequence.Add ("rules", term, true);

            ruleCompiler
                .Setup (r => r.Compile (term, context, captureCounter))
                .Returns (rule);

            return rule;
        }

        class InfixBuilder : ISequenceRuleInfixInformationBuilder {
            public IInfixInformation InfixInformation;
            public List<IRule> Rules;

            public InfixBuilder (IInfixInformation infixInformation) {
                InfixInformation = infixInformation;
                Rules = new List<IRule> ();
            }

            public void Add (IRule rule) {
                Rules.Add (rule);
            }

            public IInfixInformation CreateInfixInformation () {
                return InfixInformation;
            }
        }
    }
}